import re
import cgi
import threading
from queue_status import queue_status
import queue_globals as vars
from medusa import cgi_handler
import simplejson as json
from queue_status import statusStringToStatusCode


class handler_ah:
    patViewRequest = re.compile("/_ah/view/")
    # Empty Queue and DB names will be filtered by the regex below
    #                                          queueName/command
    patCommandRequest = re.compile("/_ah/command/([a-zA-Z0-9-_]+)/([a-zA-Z]+)/")

    validAdminCommands = [ "addQueue", "dropQueue", "addDB", "rejectProducers", "rejectConsumers",
                           "rejectAll", "resumeQueue", "dumpQueue", "loadQueue",
                           "queueList", "dbList" ]
    # Possible commands:
    # [1] addQueue
    # [2] dropQueue
    # [3] addDB
    # [4] rejectAll
    # [5] rejectProducers
    # [6] rejectConsumers
    # [7] queueList
    # [8] dbList
    #
    # These commands will be used for queue migration across/within machines: resumeQueue, dumpQueue, loadQueue
    # A queue can be dumped/loaded only if it is paused. It can be resumed only if it was paused earlier.
    # A non-existent queue can be put in the rejected state. If that is done, then it's status will not be
    # remembered if the queue/machine crashes. This is called ghost locking and can be used for queue migration.
    def __init__(self):
        self.dataStore = vars.DataStore.DataStore("master")
        if self.dataStore() is None:
            raise RuntimeError("The database '%s' does not exist or can not be opened" % "master")

        self.cv = threading.Condition()
        self.pendingRequests = [ ]
        self.workerThread = threading.Thread(target=self).start()

    def dbsOrderedByLoad(self):
        ret = []
        for db in self.dbLoad:
            ret.append((self.dbLoad[db], db))
        ret.sort(key=lambda x: x[0])
        return ret

    def updateQueueStatus(self, queueName, newStatus):
        if newStatus < 0 or newStatus > 3:
            return False
        return self.dataStore.setQueueStatus(queueName, newStatus)

    def getFileName(self, postData):
        postDict = cgi.parse_qs(postData)
        if "fileName" in postDict:
            fileName = postDict["fileName"]
            if len(fileName) > 0:
                fileName = fileName[0]
                if len(fileName) > 0 and fileName.find('/') == -1:
                    return fileName
        return None

    def cmdAddQueue(self, request, targetQueue, status = queue_status.STATUS_RUNNING):
        dbs = self.dbsOrderedByLoad()
        if len(dbs) == 0:
            request.error_enqueue(412)
            return
        targetDBId = dbs[0][1]
        insertStatus = self.dataStore.addQueue(targetQueue, targetDBId, status)
        if not insertStatus:
            request.error_enqueue(500)
            return

        self.dbLoad[targetDBId] += 1
        request.pymq_db = targetDBId
        request.pymq_queue_status = status

        # Publish this queue added event to the request_dispatcher
        vars.request_dispatcher.informQueueHandler(request)

    def handleRequest(self, request):
        # ONLY enqueue the request here.
        self.cv.acquire()
        self.pendingRequests.append(request)
        self.cv.notify()
        self.cv.release()

    def __call__(self):
        while (True):
            self.cv.acquire()
            while len(self.pendingRequests) == 0:
                self.cv.wait()
            requestsToProcess = self.pendingRequests
            self.pendingRequests = []
            self.cv.release()

            # Process the request: requestsToProcess
            for request in requestsToProcess:
                self.process_single_request(request)


    def process_single_request(self, request):
        viewRequest = handler_ah.patViewRequest.match(request.resource)
        commandRequest = handler_ah.patCommandRequest.match(request.resource)
        if viewRequest is not None:
            # Call WSGI. This is a View URL. This bit is async as of today.
            # The response will be sent back asynchronously by the CGI handler.
            wsgi = cgi_handler.MedusaCGI(request,
                                         "medusa/wsgi_handler.py",
                                         "webapp.django_main.django_main")
            wsgi.do_cgi()
            return
        elif commandRequest is not None:
            # Execute the commands
            if commandRequest.group(1) is None or commandRequest.group(2) is None:
                # Sorry dude, you screwed up....
                request.error_enqueue(404)
                return

            command = commandRequest.group(2)
            targetQueue = None
            targetDB    = None
            if command in [ "addDB", "queueList" ]:
                targetDB = commandRequest.group(1)
            else:
                targetQueue = commandRequest.group(1)

            if command not in handler_ah.validAdminCommands:
                request.error_enqueue(404)
                return

            if targetQueue is not None:
                # Check if this is a valid queue.
                result = self.dataStore.getQueueMetadata(targetQueue)
                if result is None:
                    request.error_enqueue(404)
                    return

                queueMetadata = []
                if len(result) > 0:
                    queueMetadata = result[0]
                request.pymq_type = command
                request.pymq_queue = targetQueue
            else:
                request.pymq_type = command
                request.pymq_db   = targetDB

            # Both the command AND queue name are valid
            if command == "addQueue":
                if len(queueMetadata) > 0:
                    request.error_enqueue(409) # exists!!
                    return
                self.cmdAddQueue(request, targetQueue)

            elif command == "dropQueue":
                # Check if the queue is in rejectAll state
                if not queueMetadata:
                    request.error_enqueue(404)
                    return

                if queueMetadata['status'] != queue_status.STATUS_REJECTING_ALL:
                    request.error_enqueue(412)
                    return

                deleteStatus = self.dataStore.dropQueue(targetQueue)
                if not deleteStatus:
                    request.error_enqueue(500)
                    return

                self.dbLoad[queueMetadata['ownerDB']] -= 1
                request.pymq_db = queueMetadata['ownerDB']
                # Publish this queue added event to the request_dispatcher
                vars.request_dispatcher.informQueueHandler(request)

            elif command == "addDB":
                # Note: We don't check if the database exists since we
                # fail on if any one of the steps in the middle fails.
                createDBStatus = self.dataStore.addDataStore(targetDB)
                if not createDBStatus:
                    request.error_enqueue(500)
                    return

                self.dbLoad[targetDB] = 0
                # Publish this queue added event to the request_dispatcher
                vars.request_dispatcher.informQueueHandler(request)

            elif command in [ "rejectProducers", "rejectConsumers", "rejectAll", "resumeQueue" ]:
                if command != 'rejectAll' and len(queueMetadata) == 0:
                    request.error_enqueue(404)
                    return

                newStatus = statusStringToStatusCode(command)
                if len(queueMetadata) == 0:
                    # This has to be a rejectAll command
                    # We add a queue here
                    # We should ghost lock this queue
                    request.pymq_type = "addQueue"
                    self.cmdAddQueue(request, targetQueue, newStatus)
                    # Response will come from else where
                else:
                    updateSuccess = self.updateQueueStatus(targetQueue, newStatus)
                    if not updateSuccess:
                        request.error_enqueue(500)
                    else:
                        # Publish this queue updatrd event to the request_dispatcher
                        vars.request_dispatcher.informQueueHandler(request)

            elif command in [ "dumpQueue", "loadQueue" ]:
                # This blocks for now.
                # Allow a dump/load ONLY if the queue is in the rejectAll state
                if not queueMetadata:
                    request.error_enqueue(404)
                    return

                if queueMetadata['status'] != queue_status.STATUS_REJECTING_ALL:
                    request.error_enqueue(412)
                    return

                fileName = self.getFileName(request.pymq_payload)
                if not fileName:
                    request.error_enqueue(400)
                    return

                request.pymq_payload = fileName
                vars.request_dispatcher.informQueueHandler(request)
                # Let the queue's handler send the response back

            elif command == "queueList":
                result = self.dataStore.getAllQueuesMetadata()
                if result is None:
                    request.error_enqueue(500)
                    return
                queueList = json.dumps(result)
                request.push(queueList)
                request.done_enqueue()
                return

            elif command == "dbList":
                dbList = json.dumps(self.dbLoad)
                request.push(dbList)
                request.done_enqueue()
                return

        else:
            request.error_enqueue(404)
            return


    # Returns a hash map of queue: id
    def getQueuesAndDataStores(self):
        allDataStores = self.dataStore.getAllQueueDataStores()
        self.dbLoad = { }
        dbList = []
        for dataStore in allDataStores:
            self.dbLoad[dataStore['dbName']] = 0
            dbList.append(dataStore['dbName'])

        result = self.dataStore.getAllQueuesMetadata()
        queues = { }

        for i in result:
            queues[i['queueName']] = (i['ownerDB'], i['status'])
            self.dbLoad[i['ownerDB']] += 1
        return (queues, dbList)
